package club.itstudio.itouc.jw

import club.itstudio.itouc.config.ElectiveCoursecConfig
import club.itstudio.itouc.entity.*
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.jsoup.nodes.TextNode
import org.springframework.stereotype.Component
import java.lang.Exception


/**
 * 教务系统响应数据解析
 */
@Component
class Parser (val ecConfig: ElectiveCoursecConfig){

    /**
     * 解析建议课表
     */
    fun planCourseList(data: String): List<CourseScore> {
        val elements = Jsoup.parse(data).getElementsByTag("tbody")
        val nodes = elements[0].childNodes()
        val courses = mutableListOf<CourseScore>()
        val nodeSize = nodes.size - 1
        for (i in 0 until nodeSize) {
            val classTr = nodes[i]
            val semester = if (classTr.childNode(1).childNodeSize() > 0)
                "${classTr.childNode(1).childNode(0)}" else ""
            val courseName = "${classTr.childNode(2).childNode(0)}".substring(14)
            val credit = "${classTr.childNode(3).childNode(0)}"
            val courseType = "${classTr.childNode(4).childNode(0)}"
            courses.add(CourseScore(courseName, credit, courseType, "", semester))
        }
        return courses
    }

    /**
     * 解析成绩表
     * 意外发现本人的成绩表中线性代数的课程类别为`限选课/学科基础`,实则是`必修课/公共基础`
     * 因此格外另判一下，未来可能有更多的另判情况，-_-！
     */
    val courseTypeMap = mapOf(
            "线性代数" to "必修课/公共基础"
    )
    fun scoreList(data: String): List<CourseScore> {
        val doc = Jsoup.parse(data)
        val tables = doc.getElementsByTag("table")
        val tableNum = tables.size
        val scoreList = mutableListOf<CourseScore>()
        for (i in 0 until tableNum step 2) {
            val semester = "${tables[i].childNode(1).childNode(0).childNode(1).childNode(0)}".substring(5)
            val trs = tables[i+1].childNode(3).childNodes()
            val trSize = trs.size
            for (j in 1 until trSize step 2) {
                val oneCourse = trs[j]
                val courseNameNode = oneCourse.childNode(3).childNode(0) as TextNode
                val courseName = courseNameNode.wholeText.substring(14)
                val creditNode = oneCourse.childNode(5).childNode(0) as TextNode
                val credit = creditNode.wholeText
                val courseTypeNode = oneCourse.childNode(7).childNode(0) as TextNode
                val courseType = if (courseTypeMap.containsKey(courseName))  courseTypeMap[courseName]?:""
                    else courseTypeNode.wholeText
                val scoreNode = oneCourse.childNode(13).childNode(0) as TextNode
                val score = scoreNode.wholeText
                scoreList.add(CourseScore(courseName, credit, courseType, score, semester))
            }
        }
        scoreList.reverse()
        return scoreList
    }

    /**
     * 解析加密参数
     */
    fun keyTimeParam(data: String): KeyTimeParam {
        val splice = data.split("\n")
        val key = splice[2].substring(15, 34)
        val time = splice[3].substring(16, 35)
        return KeyTimeParam(key, time)
    }

    /**
     * 解析毕业学分要求，根据通识教育/限选课
     */
    fun requiredCredit(data: String): List<Credit> {
        val creditList = mutableListOf<Credit>()
        val elements = Jsoup.parse(data).getElementsByTag("tbody").select("tr").dropLast(1)
        elements.forEach {
            var type = "${it.childNode(1).childNode(0)}"
            val splices = type.split("/")
            type = splices[1] + "/" + splices[0]
            val credit = "${it.childNode(2).childNode(0)}".toDouble()
            creditList.add(Credit(type, credit))
        }

        // 根据通识教育/限选课计算正常学分
        var multiple = 1
        creditList.forEach{
            if (it.type == "限选课/通识教育")
                multiple = (it.credit / 8).toInt()
        }
        creditList.forEach{
            it.credit /= multiple
        }
        return creditList
    }


    /**
     * 解析课程列表的响应数据解析
     * @param dataList: 响应数据列表，一页数据为一项
     */
    fun courseList(dataList: List<String>, courseType: String): List<Course> {
        val elements = mutableListOf<Element>()
        dataList.forEach {data ->
            val doc = Jsoup.parse(data)
            val dataElements = doc.getElementsByTag("tbody").select("tr")
            elements.addAll(dataElements)
        }
        val courseList = mutableListOf<Course>()
        val elementList = mutableListOf<Element>()
        elements.forEach {
            val row = it.child(0)
            val classNum = row.text().trim()
            if (classNum.isNotEmpty() && elementList.isNotEmpty()) {
                val course = parseCourse(elementList, courseType)
                // 如果不是虚拟课程
                if(course.classCode != "000000000000") courseList.add(course)
                elementList.clear()
            }
            elementList.add(it)
        }
        // 最后一节课
        if (elementList.isNotEmpty()) {
            courseList.add(parseCourse(elementList, courseType))
        }
        return courseList
    }

    /**
     * 获取选课列表总页数
     * @param data: 选课列表响应数据
     */
    fun getTotalPage(data: String): Int{
        var index = data.indexOf("DataTable.jsp")
        if(index != -1) {
            index += 15
            val pageInfo = data.substring(index, index+3).split(",")[0]
            return pageInfo.toInt()
        }
        return 1
    }

    /**
     * 解析学生选课课表
     */
    fun stuCourseList(data: String): List<StuCourse> {
        val stuCourseList = mutableListOf<StuCourse>()
        val elements = Jsoup.parse(data).getElementsByTag("tr")
        elements.removeAt(0)
        val classNameSet = mutableSetOf<String>()
        elements.forEach here@{
            val codeAndName = "${it.childNode(1).childNode(0).childNode(0)}"
            val classCode = codeAndName.substring(1, 13)
            val className = codeAndName.substring(14)
            if (classNameSet.contains(className)) return@here
            classNameSet.add(className)
            val classNum = "${it.childNode(6).childNode(0)}"
            val credit = "${it.childNode(2).childNode(0)}"
            val classType = "${it.childNode(4).childNode(0)}"
            val campus = if (it.childNode(5).childNodeSize() > 0) {// 存在校区为空的情况，特判一下
                "${it.childNode(5)?.childNode(0)}"
            } else ""
            val teacher = "${it.childNode(7).childNode(0).childNode(0)}"
            val coins = "${it.childNode(8).childNode(0)}"
            val bookCheckBox = it.childNode(9).childNode(0)
            val buyBook = ("checked" == bookCheckBox.attr("checked"))
            val retakeBox = it.childNode(10).childNode(0)
            val retake = ("checked" == retakeBox.attr("checked"))
            val adjustBox = it.childNode(11).childNode(0)
            val adjust = ("checked" == adjustBox.attr("checked"))
            val status = "${it.childNode(13).childNode(0)}"
            val remark = if(it.childNode(14).childNodeSize() > 0) {
                "${it.childNode(14).childNode(0).childNode(0)}"
            } else ""
            val course = StuCourse(classCode, classNum, className, credit, classType,
                    campus, teacher, coins, buyBook, retake, adjust, status, remark)
            stuCourseList.add(course)
        }
        return stuCourseList
    }


    /**
     * 解析为一节课
     */
    private fun parseCourse(rows: List<Element>, courseType: String): Course {
        val course:Course = parseRow(rows[0], courseType)
        val timeList = mutableListOf<CourseTime>()
        rows.forEach {
            val nowTime = parseCourseTime(it)
            var canMerge = false
            for (courseTime in timeList) {
                if(canMergeWeek(courseTime, nowTime)) {
                    // 合并周次
                    val weekList = mutableSetOf<Int>()
                    weekList.addAll(courseTime.weeks.toList())
                    weekList.addAll(nowTime.weeks.toList())
                    courseTime.weeks = weekList.toIntArray().also { ints ->  ints.sort() }
                    // 改变时间描述
                    val index = nowTime.description.indexOf("周")
                    val desc = nowTime.description.substring(0, index)
                    courseTime.description = "$desc,${courseTime.description}"
                    canMerge = true
                    break
                }
            }
            if (!canMerge) {
                for (courseTime in timeList) {
                    if (canMergeCourseRoom(courseTime, nowTime)) {
                        courseTime.classRoom += " ${nowTime.classRoom}"
                        canMerge = true
                        break
                    }
                }
            }
            if (!canMerge) timeList.add(nowTime)
        }
        course.time = timeList
        return course
    }

    /**
     * 判断是否可以合并地点
     */

    private fun canMergeCourseRoom(time1: CourseTime, time2: CourseTime): Boolean {
        // 周次不同
        if (!time1.weeks.contentEquals(time2.weeks)) return false
        // 星期不同
        if (time1.day != time2.day) return false
        // 节数不同
        if (!time1.lesson.contentEquals(time2.lesson)) return false
        return true
    }

    /**
     * 判断一节课的两条数据中的周次是否可以合并，根据每周上课时间、地点判断
     */
    private fun canMergeWeek(time1: CourseTime, time2: CourseTime): Boolean {
        // 天数不一致
        if (time1.day != time2.day) return false
        // 地点不一致
        if(time1.classRoom != time2.classRoom) return false
        // 上课时间不一致
        val lesson1 = time1.lesson
        val lesson2 = time2.lesson
        if (!lesson1.contentEquals(lesson2)) return false
        return true
    }

    /**
     * 解析选课一节课中一行的数据
     */
    private fun parseRow(row: Element, courseType: String): Course{
        val classNum = row.child(0).text().trim()
        val classCodeAndName = row.child(1).text().trim()
        val classCode = if (classCodeAndName.length >= 13) classCodeAndName.substring(1, 13) else ""

        // 若是体育课，去掉体育二字
        val className = classCodeAndName.substring(14).let {
            if (it.startsWith("体育(")) it.substring(3, it.length - 1)
            else it
        }
        val campus = row.child(2).text().trim()
        val teachers = row.child(5).text().trim().split(" ")
        val credit = row.child(7).text().trim()
        val limitNum = row.child(9).text().trim().toInt()
        val nomNum = row.child(10).text().trim().toInt()
        val determineNum = row.child(11).text().trim().toInt()

        // 判断课程类型
        val type = when(courseType) {
            // 通识课判断是否是Mooc
            CourseType.Common.name -> {
                val remark = row.child(18).text().trim()
                if (remark.startsWith("MOOC")) CourseType.CommonMooc.name
                else courseType
            }
            else -> courseType
        }
        return Course(classCode, classNum, className, campus, teachers, limitNum, nomNum, determineNum, credit, type = type)
    }

    /**
     * 解析一节课的一行数据的时间
     */
    private fun parseCourseTime(row: Element): CourseTime {
        var timeDesc = row.child(12).text().trim()
        val weeks = parseCourseWeek(timeDesc)
        // 精简时间描述
        if (ecConfig.semester == "0" && timeDesc.startsWith("1-4周 ")) { // 夏季学期
            timeDesc = "周${timeDesc.substring(5)}"
        }
        if (timeDesc.startsWith("1-17周 ") || timeDesc.startsWith("2-17周")) {
            timeDesc = "周${timeDesc.substring(5)}"
        }
        val weekArray = weeks.toIntArray().also {it.sort()}
        val lessons = parseLesson(timeDesc)
        val lessonArray = lessons.toIntArray().also { it.sort()}
        val day = parseDay(timeDesc)
        val classRoom = row.child(13).text().trim()
        val remark = row.child(18).text().trim()
        return CourseTime(timeDesc, weekArray, classRoom, day, lessonArray, remark)
    }

    /**
     * 解析一节课的周次
     */
    private fun parseCourseWeek(timeDesc: String): Set<Int> {
        val weeks = mutableSetOf<Int>()    // 上课周次
        // 解析周次,以"周"分隔开
        val weekInfo = timeDesc.substring(0, timeDesc.indexOf("周"))
        // 单双周标志
        val flag = timeDesc[(timeDesc.length - 1)]
        // 偏移量
        var offset = 1
        if (flag == '单' || flag == '双') {
            offset = 2
        }

        // 按照1，3-7，9-17这种时间格式进行解析
        val weekSplits = weekInfo.split("[,.]".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        for (weekSplit in weekSplits) {
            val weekDistance = weekSplit.split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
            var startWeek = Integer.parseInt(weekDistance[0])
            val endWeek = Integer.parseInt(weekDistance[weekDistance.size - 1])
            if (flag == '单') {
                startWeek += (startWeek % 2).inv() + 2
            } else if (flag == '双') {
                startWeek += startWeek % 2
            }
            var nowWeek = startWeek
            while (nowWeek <= endWeek) {
                weeks.add(nowWeek)
                nowWeek += offset
            }
        }
        return weeks
    }

    /**
     * 解析每天上哪几节课
     */
    private fun parseLesson(time: String): List<Int>{
        val startIndex = time.indexOf("(") + 1
        val endIndex = time.indexOf("节")
        val lessons = mutableListOf<Int>()
        if (startIndex == -1 || endIndex == -1) {
            return lessons
        }
        val nums = time.substring(startIndex, endIndex).split("-")
        val start = Integer.parseInt(nums[0])
        val end = Integer.parseInt(nums[nums.size - 1])
        for (now in start..end) {
            lessons.add(now)
        }
        return lessons
    }

    /**
     * 解析星期几
     */
    private fun parseDay(timeDesc: String): Int {
        val index = timeDesc.indexOf("周") + 1
        return if (timeDesc.length < index + 1)  0
        else {
            when(timeDesc[index]) {
                '一' -> 1
                '二' -> 2
                '三' -> 3
                '四' -> 4
                '五' -> 5
                '六' -> 6
                '日' -> 7
                else -> 0
            }
        }
    }
}


